home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Open Source / AutoHotKey / Source / AutoHotkey104705_source.exe / source / script_menu.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-01-01  |  59.9 KB  |  1,373 lines

  1. /*
  2. AutoHotkey
  3.  
  4. Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
  5.  
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15. */
  16.  
  17. #include "stdafx.h" // pre-compiled headers
  18. #include "script.h"
  19. #include "globaldata.h" // for a lot of things
  20. #include "application.h" // for MsgSleep()
  21. #include "window.h" // for SetForegroundWindowEx()
  22.  
  23.  
  24. ResultType Script::PerformMenu(char *aMenu, char *aCommand, char *aParam3, char *aParam4, char *aOptions)
  25. {
  26.     if (mMenuUseErrorLevel)
  27.         g_ErrorLevel->Assign(ERRORLEVEL_NONE);  // Set default, which is "none" for the Menu command.
  28.  
  29.     #define RETURN_MENU_ERROR(msg, info) return mMenuUseErrorLevel ? g_ErrorLevel->Assign(ERRORLEVEL_ERROR) \
  30.         : ScriptError(msg ERR_ABORT, info)
  31.     #define RETURN_IF_NOT_TRAY if (!is_tray) RETURN_MENU_ERROR(ERR_MENUTRAY, aMenu)
  32.  
  33.     MenuCommands menu_command = Line::ConvertMenuCommand(aCommand);
  34.     if (menu_command == MENU_CMD_INVALID)
  35.         RETURN_MENU_ERROR(ERR_PARAM2_INVALID, aCommand);
  36.  
  37.     bool is_tray = !stricmp(aMenu, "tray");
  38.  
  39.     // Handle early on anything that doesn't require the menu to be found or created:
  40.     switch(menu_command)
  41.     {
  42.     case MENU_CMD_USEERRORLEVEL:
  43.         mMenuUseErrorLevel = (Line::ConvertOnOff(aParam3) != TOGGLED_OFF);
  44.         // Even though the state may have changed by the above, it doesn't seem necessary
  45.         // to adjust on the fly for the purpose of this particular return.  In other words,
  46.         // the old mode will be in effect for this one return:
  47.         return OK;
  48.  
  49.     case MENU_CMD_TIP:
  50.         RETURN_IF_NOT_TRAY;
  51.         if (*aParam3)
  52.         {
  53.             if (!mTrayIconTip)
  54.                 mTrayIconTip = SimpleHeap::Malloc(sizeof(mNIC.szTip)); // SimpleHeap improves avg. case mem load.
  55.             if (mTrayIconTip)
  56.                 strlcpy(mTrayIconTip, aParam3, sizeof(mNIC.szTip));
  57.         }
  58.         else // Restore tip to default.
  59.             if (mTrayIconTip)
  60.                 *mTrayIconTip = '\0';
  61.         if (mNIC.hWnd) // i.e. only update the tip if the tray icon exists (can't work otherwise).
  62.         {
  63.             UPDATE_TIP_FIELD
  64.             Shell_NotifyIcon(NIM_MODIFY, &mNIC);  // Currently not checking its result (e.g. in case a shell other than Explorer is running).
  65.         }
  66.         return OK;
  67.  
  68.     case MENU_CMD_ICON:
  69.     {
  70.         RETURN_IF_NOT_TRAY;
  71.         bool mIconFrozen_prev = mIconFrozen;
  72.         if (*aOptions) // i.e. if it's blank, don't change the current setting of mIconFrozen.
  73.             mIconFrozen = (ATOI(aOptions) == 1);
  74.         if (!*aParam3)
  75.         {
  76.             g_NoTrayIcon = false;
  77.             if (!mNIC.hWnd) // The icon doesn't exist, so create it.
  78.             {
  79.                 CreateTrayIcon();
  80.                 UpdateTrayIcon(true);  // Force the icon into the correct pause/suspend state.
  81.             }
  82.             else if (!mIconFrozen && mIconFrozen_prev) // To cause "Menu Tray, Icon,,, 0" to update the icon while the script is suspended.
  83.                 UpdateTrayIcon(true);
  84.             return OK;
  85.         }
  86.  
  87.         // Otherwise, user has specified a custom icon:
  88.         if (*aParam3 == '*' && !*(aParam3 + 1)) // Restore the standard icon.
  89.         {
  90.             if (mCustomIcon)
  91.             {
  92.                 GuiType::DestroyIconIfUnused(mCustomIcon); // v1.0.37.07: Solves reports of Gui windows losing their icons.
  93.                 // If the above doesn't destroy the icon, the GUI window(s) still using it are responsible for
  94.                 // destroying it later.
  95.                 mCustomIcon = NULL;  // To indicate that there is no custom icon.
  96.                 if (mCustomIconFile)
  97.                     *mCustomIconFile = '\0';
  98.                 mCustomIconNumber = 0;
  99.                 UpdateTrayIcon(true);  // Need to use true in this case too.
  100.             }
  101.             return OK;
  102.         }
  103.  
  104.         // v1.0.43.03: Load via LoadPicture() vs. ExtractIcon() because ExtractIcon harms the quality
  105.         // of 16x16 icons inside .ico files by first scaling them to 32x32 (which then has to be scaled
  106.         // back to 16x16 for the tray and for the SysMenu icon). I've visually confirmed that the
  107.         // distortion occurs at least when a 16x16 icon is loaded by ExtractIcon() then put into the
  108.         // tray.  It might not be the scaling itself that distorts the icon: the pixels are all in the
  109.         // right places, it's just that some are the wrong color/shade. This implies that some kind of
  110.         // unwanted interpolation or color tweaking is being done by ExtractIcon (and probably LoadIcon),
  111.         // but not by LoadImage.
  112.         // Also, load the icon at actual size so that when/if this icon is used for a GUI window, its
  113.         // appearance in the alt-tab menu won't be unexpectedly poor due to having been scaled from its
  114.         // native size down to 16x16.
  115.         int icon_number;
  116.         if (*aParam4)
  117.         {
  118.             icon_number = ATOI(aParam4);
  119.             if (icon_number < 1) // Must validate for use in two places below.
  120.                 icon_number = 1; // Must be >0 to tell LoadPicture that "icon must be loaded, never a bitmap".
  121.         }
  122.         else
  123.             icon_number = 1; // One vs. Zero tells LoadIcon: "must load icon, never a bitmap (e.g. no gif/jpg/png)".
  124.  
  125.         int image_type;
  126.         HICON new_icon;
  127.         if (   !(new_icon = (HICON)LoadPicture(aParam3, 0, 0, image_type, icon_number, false))   ) // Called with icon_number > 0, it guarantees return of an HICON/HCURSOR, never an HBITMAP.
  128.             RETURN_MENU_ERROR("Can't load icon.", aParam3);
  129.         GuiType::DestroyIconIfUnused(mCustomIcon); // This destroys it if non-NULL and it's not used by an GUI windows.
  130.  
  131.         mCustomIcon = new_icon;
  132.         mCustomIconNumber = icon_number;
  133.         // Allocate the full MAX_PATH in case the contents grow longer later.
  134.         // SimpleHeap improves avg. case mem load:
  135.         if (!mCustomIconFile)
  136.             mCustomIconFile = SimpleHeap::Malloc(MAX_PATH);
  137.         if (mCustomIconFile)
  138.         {
  139.             // Get the full path in case it's a relative path.  This is documented and it's done in case
  140.             // the script ever changes its working directory:
  141.             char full_path[MAX_PATH], *filename_marker;
  142.             if (GetFullPathName(aParam3, sizeof(full_path) - 1, full_path, &filename_marker))
  143.                 strlcpy(mCustomIconFile, full_path, MAX_PATH);
  144.             else
  145.                 strlcpy(mCustomIconFile, aParam3, MAX_PATH);
  146.         }
  147.  
  148.         if (!g_NoTrayIcon)
  149.             UpdateTrayIcon(true);  // Need to use true in this case too.
  150.         return OK;
  151.     }
  152.  
  153.     case MENU_CMD_NOICON:
  154.         RETURN_IF_NOT_TRAY;
  155.         g_NoTrayIcon = true;
  156.         if (mNIC.hWnd) // Since it exists, destroy it.
  157.         {
  158.             Shell_NotifyIcon(NIM_DELETE, &mNIC); // Remove it.
  159.             mNIC.hWnd = NULL;  // Set this as an indicator that tray icon is not installed.
  160.             // but don't do DestroyMenu() on mTrayMenu->mMenu (if non-NULL) since it may have been
  161.             // changed by the user to have the custom items on top of the standard items,
  162.             // for example, and we don't want to lose that ordering in case the script turns
  163.             // the icon back on at some future time during this session.
  164.         }
  165.         return OK;
  166.  
  167.     case MENU_CMD_CLICK:
  168.         RETURN_IF_NOT_TRAY;
  169.         mTrayMenu->mClickCount = ATOI(aParam3);
  170.         if (mTrayMenu->mClickCount < 1)
  171.             mTrayMenu->mClickCount = 1;  // Single-click to activate menu's default item.
  172.         else if (mTrayMenu->mClickCount > 2)
  173.             mTrayMenu->mClickCount = 2;  // Double-click.
  174.         return OK;
  175.  
  176.     case MENU_CMD_MAINWINDOW:
  177.         RETURN_IF_NOT_TRAY;
  178. #ifdef AUTOHOTKEYSC
  179.         if (!g_AllowMainWindow)
  180.         {
  181.             g_AllowMainWindow = true;
  182.             // Rather than using InsertMenu() to insert the item in the right position,
  183.             // which makes the code rather unmaintainable, it seems best just to recreate
  184.             // the entire menu.  This will result in the standard menu items going back
  185.             // up to the top of the menu if the user previously had them at the bottom,
  186.             // but it seems too rare to worry about, especially since it's easy to
  187.             // work around that:
  188.             if (mTrayMenu->mIncludeStandardItems)
  189.                 mTrayMenu->Destroy(); // It will be recreated automatically the next time the user displays it.
  190.             // else there's no need.
  191.         }
  192. #endif
  193.         return OK;
  194.  
  195.     case MENU_CMD_NOMAINWINDOW:
  196.         RETURN_IF_NOT_TRAY;
  197. #ifdef AUTOHOTKEYSC
  198.         if (g_AllowMainWindow)
  199.         {
  200.             g_AllowMainWindow = false;
  201.             // See comments in the prior case above for why it's done this way vs. using DeleteMenu():
  202.             if (mTrayMenu->mIncludeStandardItems)
  203.                 mTrayMenu->Destroy(); // It will be recreated automatically the next time the user displays it.
  204.             // else there's no need.
  205.         }
  206. #endif
  207.         return OK;
  208.     } // switch()
  209.  
  210.  
  211.     // Now that most opportunities to return an error have passed, find or create the menu, since
  212.     // all the commands that haven't already been fully handled above will need it:
  213.     UserMenu *menu = FindMenu(aMenu);
  214.     if (!menu)
  215.     {
  216.         // Menus can be created only in conjuction with the ADD command. Update: As of v1.0.25.12, they can
  217.         // also be created with the "Menu, MyMenu, Standard" command.
  218.         if (menu_command != MENU_CMD_ADD && menu_command != MENU_CMD_STANDARD)
  219.             RETURN_MENU_ERROR(ERR_MENU, aMenu);
  220.         if (   !(menu = AddMenu(aMenu))   )
  221.             RETURN_MENU_ERROR("Menu name too long.", aMenu); // Could also be "out of mem" but that's too rare to display.
  222.     }
  223.  
  224.     // The above has found or added the menu for use below.
  225.  
  226.     switch(menu_command)
  227.     {
  228.     case MENU_CMD_SHOW:
  229.         return menu->Display(true, *aParam3 ? ATOI(aParam3) : COORD_UNSPECIFIED, *aParam4 ? ATOI(aParam4) : COORD_UNSPECIFIED);
  230.     case MENU_CMD_ADD:
  231.         if (*aParam3) // Since a menu item name was given, it's not a separator line.
  232.             break;    // Let a later switch() handle it.
  233.         if (!menu->AddItem("", 0, NULL, NULL, ""))
  234.             RETURN_MENU_ERROR(ERR_OUTOFMEM, "");  // Out of mem should be the only possibility in this case.
  235.         return OK;
  236.     case MENU_CMD_DELETE:
  237.         if (*aParam3) // Since a menu item name was given, an item is being deleted, not the whole menu.
  238.             break;    // Let a later switch() handle it.
  239.         if (menu == mTrayMenu)
  240.             RETURN_MENU_ERROR("Tray menu must not be deleted.", "");
  241.         if (!ScriptDeleteMenu(menu))
  242.             RETURN_MENU_ERROR("Can't delete menu (in use?).", menu->mName); // Possibly in use as a menu bar.
  243.         return OK;
  244.     case MENU_CMD_DELETEALL:
  245.         if (!menu->DeleteAllItems())
  246.             RETURN_MENU_ERROR("Can't delete items (in use?).", menu->mName); // Possibly in use as a menu bar.
  247.         return OK;
  248.     case MENU_CMD_DEFAULT:
  249.         if (*aParam3) // Since a menu item has been specified, let a later switch() handle it.
  250.             break;
  251.         //else no menu item, so it's the same as NoDefault: fall through to the next case.
  252.     case MENU_CMD_NODEFAULT:
  253.         return menu->SetDefault();
  254.     case MENU_CMD_STANDARD:
  255.         menu->IncludeStandardItems(); // Since failure is very rare, no check of its return value is done.
  256.         return OK;
  257.     case MENU_CMD_NOSTANDARD:
  258.         menu->ExcludeStandardItems(); // Since failure is very rare, no check of its return value is done.
  259.         return OK;
  260.     case MENU_CMD_COLOR:
  261.         menu->SetColor(aParam3, stricmp(aParam4, "Single"));
  262.         return OK;
  263.     }
  264.  
  265.     // All the remaining commands need a menu item to operate upon, or some other requirement met below.
  266.  
  267.     char *new_name = "";
  268.     if (menu_command == MENU_CMD_RENAME) // aParam4 contains the menu item's new name in this case.
  269.     {
  270.         new_name = aParam4;
  271.         aParam4 = "";
  272.     }
  273.  
  274.     // The above has handled all cases that don't require a menu item to be found or added,
  275.     // including the adding separator lines.  So at the point, it is necessary to either find
  276.     // or create a menu item.  The latter only occurs for the ADD command.
  277.     if (!*aParam3)
  278.         RETURN_MENU_ERROR("Parameter #3 must not be blank in this case.", "");
  279.  
  280.     // Find the menu item name AND its previous item (needed for the DELETE command) in the linked list:
  281.     UserMenuItem *mi, *menu_item = NULL, *menu_item_prev = NULL; // Set defaults.
  282.     for (menu_item = menu->mFirstMenuItem
  283.         ; menu_item
  284.         ; menu_item_prev = menu_item, menu_item = menu_item->mNextMenuItem)
  285.         if (!lstrcmpi(menu_item->mName, aParam3)) // Match found (case insensitive).
  286.             break;
  287.  
  288.     // Whether an existing menu item's options should be updated without updating its submenu or label:
  289.     bool update_exiting_item_options = (menu_command == MENU_CMD_ADD && menu_item && !*aParam4 && *aOptions);
  290.  
  291.     // Seems best to avoid performance enhancers such as (Label *)mAttribute here, since the "Menu"
  292.     // command has so many modes of operation that would be difficult to parse at load-time:
  293.     Label *target_label = NULL;  // Set default.
  294.     UserMenu *submenu = NULL;    // Set default.
  295.     if (menu_command == MENU_CMD_ADD && !update_exiting_item_options) // Labels and submenus are only used in conjuction with the ADD command.
  296.     {
  297.         if (!*aParam4) // Allow the label/submenu to default to the menu name.
  298.             aParam4 = aParam3; // Note that aParam3 will be blank in the case of a separator line.
  299.         if (*aParam4)
  300.         {
  301.             if (*aParam4 == ':') // It's a submenu.
  302.             {
  303.                 ++aParam4;
  304.                 if (   !(submenu = FindMenu(aParam4))   )
  305.                     RETURN_MENU_ERROR(ERR_SUBMENU, aParam4);
  306.                 // Before going further: since a submenu has been specified, make sure that the parent
  307.                 // menu is not included anywhere in the nested hierarchy of that submenu's submenus.
  308.                 // The OS doesn't seem to like that, creating empty or strange menus if it's attempted:
  309.                 if (   submenu && (submenu == menu || submenu->ContainsMenu(menu))   )
  310.                     RETURN_MENU_ERROR("Submenu must not contain its parent menu.", aParam4);
  311.             }
  312.             else // It's a label.
  313.                 if (   !(target_label = FindLabel(aParam4))   )
  314.                     RETURN_MENU_ERROR(ERR_NO_LABEL, aParam4);
  315.         }
  316.     }
  317.  
  318.     if (!menu_item)  // menu item doesn't exist, so create it (but only if the command is ADD).
  319.     {
  320.         if (menu_command != MENU_CMD_ADD)
  321.             // Seems best not to create menu items on-demand like this because they might get put into
  322.             // an incorrect position (i.e. it seems better than menu changes be kept separate from
  323.             // menu additions):
  324.             RETURN_MENU_ERROR("Nonexistent menu item.", aParam3);
  325.  
  326.         // Otherwise: Adding a new item that doesn't yet exist.
  327.         // Need to find a menuID that isn't already in use by one of the other menu items.
  328.         // But also need to conserve menu items since only a relatively small number of IDs is available.
  329.         // Can't simply use ID_USER_FIRST + mMenuItemCount because: 1) There might be more than one
  330.         // user defined menu; 2) a menu item in the middle of the list may have been deleted,
  331.         // in which case that value would already be in use by the last item.
  332.         // Update: Now using caching of last successfully found free-ID to greatly improve avg.
  333.         // performance, especially for menus that contain thousands of items and submenus, such as
  334.         // ones that are built to mirror an entire nested directory structure.  Caching should
  335.         // improve performance even after all menu IDs within the available range have been
  336.         // allocated once (via adding and deleting menus + menu items) since large blocks of free IDs
  337.         // should be free, and on average, the caching will exploit these large free blocks.  However,
  338.         // if large amounts of menus and menu items are continually deleted and re-added by a script,
  339.         // the pool of free IDs will become fragmented over time, which will reduce performance.
  340.         // Since that kind of script behavior seems very rare, no attempt is made to "defragment".
  341.         // If more performance is needed in the future (seems unlikely for 99.9999% of scripts),
  342.         // could maintain an field of ~64000 bits, each bit representing whether a menu item ID is
  343.         // free.  Then, every time a menu or one or more of its IDs is deleted or added, the corresponding
  344.         // ID could be marked as free/taken.  That would add quite a bit of complexity to the menu
  345.         // delete code, however, and it would reduce the overall maintainability.  So it definitely
  346.         // doesn't seem worth it, especially since Windows XP seems to have trouble even displaying
  347.         // menus larger than around 15000-25000 items.
  348.         static UINT sLastFreeID = ID_USER_FIRST - 1;
  349.         // Increment by one for each new search, both due to the above line and because the
  350.         // last-found free ID has a high likelyhood of still being in use:
  351.         ++sLastFreeID;
  352.         bool id_in_use;
  353.         UserMenu *m;
  354.         // Note that the i variable is used to force the loop to complete exactly one full
  355.         // circuit through all available IDs, regardless of where the starting/cached value:
  356.         for (int i = 0; i < (ID_USER_LAST - ID_USER_FIRST + 1); ++i, ++sLastFreeID) // FOR EACH ID
  357.         {
  358.             if (sLastFreeID > ID_USER_LAST)
  359.                 sLastFreeID = ID_USER_FIRST;  // Wrap around to the beginning so that one complete circuit is made.
  360.             id_in_use = false;  // Reset the default each iteration (overridden if the below finds a match).
  361.             for (m = mFirstMenu; m; m = m->mNextMenu) // FOR EACH MENU
  362.             {
  363.                 for (mi = m->mFirstMenuItem; mi; mi = mi->mNextMenuItem) // FOR EACH MENU ITEM
  364.                 {
  365.                     if (mi->mMenuID == sLastFreeID)
  366.                     {
  367.                         id_in_use = true;
  368.                         break;
  369.                     }
  370.                 }
  371.                 if (id_in_use) // No point in searching the other menus, since it's now known to be in use.
  372.                     break;
  373.             }
  374.             if (!id_in_use) // Break before the loop increments sLastFreeID.
  375.                 break;
  376.         }
  377.         if (id_in_use) // All ~64000 IDs are in use!
  378.             RETURN_MENU_ERROR("Too many menu items.", aParam3); // Short msg since so rare.
  379.         if (!menu->AddItem(aParam3, sLastFreeID, target_label, submenu, aOptions))
  380.             RETURN_MENU_ERROR("Menu item name too long.", aParam3); // Can also happen due to out-of-mem, but that's too rare to display.
  381.         return OK;  // Item has been successfully added with the correct properties.
  382.     } // if (!menu_item)
  383.  
  384.     // Above has found the correct menu_item to operate upon (it already returned if
  385.     // the item was just created).  Since the item was found, the UserMenu's popup
  386.     // menu must already exist because a UserMenu object can't have menu items unless
  387.     // its menu exists.
  388.  
  389.     switch (menu_command)
  390.     {
  391.     case MENU_CMD_ADD:
  392.         // This is only reached if the ADD command is being used to update the label, submenu, or
  393.         // options of an existing menu item (since it would have returned above if the item was
  394.         // just newly created).
  395.         return menu->ModifyItem(menu_item, target_label, submenu, aOptions);
  396.     case MENU_CMD_RENAME:
  397.         if (!menu->RenameItem(menu_item, new_name))
  398.             RETURN_MENU_ERROR("Menu item name already in use (or too long).", new_name);
  399.         return OK;
  400.     case MENU_CMD_CHECK:
  401.         return menu->CheckItem(menu_item);
  402.     case MENU_CMD_UNCHECK:
  403.         return menu->UncheckItem(menu_item);
  404.     case MENU_CMD_TOGGLECHECK:
  405.         return menu->ToggleCheckItem(menu_item);
  406.     case MENU_CMD_ENABLE:
  407.         return menu->EnableItem(menu_item);
  408.     case MENU_CMD_DISABLE: // Disables and grays the item.
  409.         return menu->DisableItem(menu_item);
  410.     case MENU_CMD_TOGGLEENABLE:
  411.         return menu->ToggleEnableItem(menu_item);
  412.     case MENU_CMD_DEFAULT:
  413.         return menu->SetDefault(menu_item);
  414.     case MENU_CMD_DELETE:
  415.         return menu->DeleteItem(menu_item, menu_item_prev);
  416.     } // switch()
  417.     return FAIL;  // Should never be reached, but avoids compiler warning and improves bug detection.
  418. }
  419.  
  420.  
  421.  
  422. UserMenu *Script::FindMenu(char *aMenuName)
  423. // Returns the UserMenu whose name matches aMenuName, or NULL if not found.
  424. {
  425.     if (!aMenuName || !*aMenuName) return NULL;
  426.     for (UserMenu *menu = mFirstMenu; menu != NULL; menu = menu->mNextMenu)
  427.         if (!lstrcmpi(menu->mName, aMenuName)) // Match found.
  428.             return menu;
  429.     return NULL; // No match found.
  430. }
  431.  
  432.  
  433.  
  434. UserMenu *Script::AddMenu(char *aMenuName)
  435. // Caller must have already ensured aMenuName doesn't exist yet in the list.
  436. // Returns the newly created UserMenu object.
  437. {
  438.     if (!aMenuName || !*aMenuName) return NULL;
  439.     size_t length = strlen(aMenuName);
  440.     if (length > MAX_MENU_NAME_LENGTH)
  441.         return NULL;  // Caller should show error if desired.
  442.     // After mem is allocated, the object takes charge of its later deletion:
  443.     char *name_dynamic = new char[length + 1];  // +1 for terminator.
  444.     if (!name_dynamic)
  445.         return NULL;  // Caller should show error if desired.
  446.     strcpy(name_dynamic, aMenuName);
  447.     UserMenu *menu = new UserMenu(name_dynamic);
  448.     if (!menu)
  449.     {
  450.         delete name_dynamic;
  451.         return NULL;  // Caller should show error if desired.
  452.     }
  453.     if (!mFirstMenu)
  454.         mFirstMenu = mLastMenu = menu;
  455.     else
  456.     {
  457.         mLastMenu->mNextMenu = menu;
  458.         // This must be done after the above:
  459.         mLastMenu = menu;
  460.     }
  461.     ++mMenuCount;  // Only after memory has been successfully allocated.
  462.     return menu;
  463. }
  464.  
  465.  
  466.  
  467. ResultType Script::ScriptDeleteMenu(UserMenu *aMenu)
  468. // Deletes a UserMenu object and all the UserMenuItem objects that belong to it.
  469. // Any UserMenuItem object that has a submenu attached to it does not result in
  470. // that submenu being deleted, even if no other menus are using that submenu
  471. // (i.e. the user must delete all menus individually).  Any menus which have
  472. // aMenu as one of their submenus will have that menu item deleted from their
  473. // menus to avoid any chance of problems due to non-existent or NULL submenus.
  474. {
  475.     // Delete any other menu's menu item that has aMenu as its attached submenu:
  476.     UserMenuItem *mi, *mi_prev, *mi_to_delete;
  477.     for (UserMenu *m = mFirstMenu; m; m = m->mNextMenu)
  478.         if (m != aMenu) // Don't bother with this menu even if it's submenu of itself, since it will be destroyed anyway.
  479.             for (mi = m->mFirstMenuItem, mi_prev = NULL; mi;)
  480.             {
  481.                 mi_to_delete = mi;
  482.                 mi = mi->mNextMenuItem;
  483.                 if (mi_to_delete->mSubmenu == aMenu)
  484.                     m->DeleteItem(mi_to_delete, mi_prev);
  485.                 else
  486.                     mi_prev = mi_to_delete;
  487.             }
  488.     // Remove aMenu from the linked list.  First find the item that occurs prior the aMenu in the list:
  489.     UserMenu *aMenu_prev;
  490.     for (aMenu_prev = mFirstMenu; aMenu_prev; aMenu_prev = aMenu_prev->mNextMenu)
  491.         if (aMenu_prev->mNextMenu == aMenu)
  492.             break;
  493.     if (aMenu == mLastMenu)
  494.         mLastMenu = aMenu_prev; // Can be NULL if the list will now be empty.
  495.     if (aMenu_prev) // there is another item prior to aMenu in the linked list.
  496.         aMenu_prev->mNextMenu = aMenu->mNextMenu; // Can be NULL if aMenu was the last one.
  497.     else // aMenu was the first one in the list.
  498.         mFirstMenu = aMenu->mNextMenu; // Can be NULL if the list will now be empty.
  499.     // Do this last when its contents are no longer needed.  Its destructor will delete all
  500.     // the items in the menu and destroy the OS menu itself:
  501.     aMenu->DeleteAllItems(); // This also calls Destroy() to free the menu's resources.
  502.     if (aMenu->mBrush) // Free the brush used for the menu's background color.
  503.         DeleteObject(aMenu->mBrush);
  504.     delete aMenu->mName; // Since it was separately allocated.
  505.     delete aMenu;
  506.     --mMenuCount;
  507.     return OK;
  508. }
  509.  
  510.  
  511.  
  512. // Macros for use with the below methods:
  513. #define aMenuItem_ID (aMenuItem->mSubmenu ? GetSubmenuPos(aMenuItem->mSubmenu->mMenu) : aMenuItem->mMenuID)
  514. #define aMenuItem_MF_BY (aMenuItem->mSubmenu ? MF_BYPOSITION : MF_BYCOMMAND)
  515. #define UPDATE_GUI_MENU_BARS(menu_type, hmenu) \
  516.     if (menu_type == MENU_TYPE_BAR && GuiType::sGuiCount)\
  517.         GuiType::UpdateMenuBars(hmenu); // Above: If it's not a popup, it's probably a menu bar.
  518.  
  519.  
  520. #ifdef AUTOHOTKEYSC
  521. #define CHANGE_DEFAULT_IF_NEEDED \
  522.     if (mDefault == aMenuItem)\
  523.     {\
  524.         if (mMenu)\
  525.         {\
  526.             if (this == g_script.mTrayMenu)\
  527.                 SetMenuDefaultItem(mMenu, mIncludeStandardItems && g_AllowMainWindow ? ID_TRAY_OPEN : -1, FALSE);\
  528.             else\
  529.                 SetMenuDefaultItem(mMenu, -1, FALSE);\
  530.         }\
  531.         mDefault = NULL;\
  532.     }
  533. #else
  534. #define CHANGE_DEFAULT_IF_NEEDED \
  535.     if (mDefault == aMenuItem)\
  536.     {\
  537.         if (mMenu)\
  538.         {\
  539.             if (this == g_script.mTrayMenu)\
  540.                 SetMenuDefaultItem(mMenu, mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);\
  541.             else\
  542.                 SetMenuDefaultItem(mMenu, -1, FALSE);\
  543.         }\
  544.         mDefault = NULL;\
  545.     }
  546. #endif
  547.  
  548.  
  549.  
  550. ResultType UserMenu::AddItem(char *aName, UINT aMenuID, Label *aLabel, UserMenu *aSubmenu, char *aOptions)
  551. // Caller must have already ensured that aName does not yet exist as a user-defined menu item
  552. // in this->mMenu.
  553. {
  554.     size_t length = strlen(aName);
  555.     if (length > MAX_MENU_NAME_LENGTH)
  556.         return FAIL;  // Caller should show error if desired.
  557.     // After mem is allocated, the object takes charge of its later deletion:
  558.     char *name_dynamic;
  559.     if (length)
  560.     {
  561.         if (   !(name_dynamic = new char[length + 1])   )  // +1 for terminator.
  562.             return FAIL;  // Caller should show error if desired.
  563.         strcpy(name_dynamic, aName);
  564.     }
  565.     else
  566.         name_dynamic = Var::sEmptyString; // So that it can be detected as a non-allocated empty string.
  567.     UserMenuItem *menu_item = new UserMenuItem(name_dynamic, length + 1, aMenuID, aLabel, aSubmenu, this);
  568.     if (!menu_item) // Should also be very rare.
  569.     {
  570.         if (name_dynamic != Var::sEmptyString)
  571.             delete name_dynamic;
  572.         return FAIL;  // Caller should show error if desired.
  573.     }
  574.     if (!mFirstMenuItem)
  575.         mFirstMenuItem = mLastMenuItem = menu_item;
  576.     else
  577.     {
  578.         mLastMenuItem->mNextMenuItem = menu_item;
  579.         // This must be done after the above:
  580.         mLastMenuItem = menu_item;
  581.     }
  582.     ++mMenuItemCount;  // Only after memory has been successfully allocated.
  583.     if (*aOptions)
  584.         UpdateOptions(menu_item, aOptions);
  585.     return OK;
  586. }
  587.  
  588.  
  589.  
  590. UserMenuItem::UserMenuItem(char *aName, size_t aNameCapacity, UINT aMenuID, Label *aLabel, UserMenu *aSubmenu, UserMenu *aMenu)
  591. // UserMenuItem Constructor.
  592.     : mName(aName), mNameCapacity(aNameCapacity), mMenuID(aMenuID), mLabel(aLabel), mSubmenu(aSubmenu), mMenu(aMenu)
  593.     , mPriority(0) // default priority = 0
  594.     , mEnabled(true), mChecked(false), mNextMenuItem(NULL)
  595. {
  596.     if (aMenu->mMenu)
  597.     {
  598.         if (aSubmenu) // Ensure the menu is created so that AppendMenu() will function properly.
  599.             aSubmenu->Create();
  600.         AppendMenu(aMenu->mMenu, (*aName ? MF_STRING : MF_SEPARATOR) | (aSubmenu ? MF_POPUP : 0)
  601.             , aSubmenu ? (UINT_PTR)aSubmenu->mMenu : aMenuID, aName);
  602.         UPDATE_GUI_MENU_BARS(aMenu->mMenuType, aMenu->mMenu)
  603.     }
  604. }
  605.  
  606.  
  607.  
  608. ResultType UserMenu::DeleteItem(UserMenuItem *aMenuItem, UserMenuItem *aMenuItemPrev)
  609. {
  610.     // Remove this menu item from the linked list:
  611.     if (aMenuItem == mLastMenuItem)
  612.         mLastMenuItem = aMenuItemPrev; // Can be NULL if the list will now be empty.
  613.     if (aMenuItemPrev) // there is another item prior to aMenuItem in the linked list.
  614.         aMenuItemPrev->mNextMenuItem = aMenuItem->mNextMenuItem; // Can be NULL if aMenuItem was the last one.
  615.     else // aMenuItem was the first one in the list.
  616.         mFirstMenuItem = aMenuItem->mNextMenuItem; // Can be NULL if the list will now be empty.
  617.     CHANGE_DEFAULT_IF_NEEDED  // Should do this before freeing aMenuItem's memory.
  618.     if (mMenu) // Delete the item from the menu.
  619.         DeleteMenu(mMenu, aMenuItem_ID, aMenuItem_MF_BY);
  620.     if (aMenuItem->mName != Var::sEmptyString)
  621.         delete aMenuItem->mName; // Since it was separately allocated.
  622.     delete aMenuItem; // Do this last when its contents are no longer needed.
  623.     --mMenuItemCount;
  624.     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  625.     return OK;
  626. }
  627.  
  628.  
  629.  
  630. ResultType UserMenu::DeleteAllItems()
  631. {
  632.     if (!mFirstMenuItem)
  633.         return OK;  // If there are no user-defined menu items, it's already in the correct state.
  634.     // Remove all menu items from the linked list and from the menu.  First destroy the menu since
  635.     // it's probably better to start off fresh than have the destructor individually remove each
  636.     // menu item as the items in the linked list are deleted.  In addition, this avoids the need
  637.     // to find any submenus by position:
  638.     if (!Destroy())  // if mStandardMenuItems is true, the menu will be recreated later when needed.
  639.         // If menu can't be destroyed, it's probably due to it being attached as a menu bar to an existing
  640.         // GUI window.  In this case, when the window is destroyed, the menu bar will be too, so it's
  641.         // probably best to do nothing.  If we were called as a result of "menu, MenuName, Delete", it
  642.         // is documented that this will fail in this case.
  643.         return FAIL;
  644.     // The destructor relies on the fact that the above destroys the menu but does not recreate it.
  645.     // This is because popup menus, not being affiliated with a window (unless they're attached to
  646.     // a menu bar, but that isn't the case with our GUI windows, which detach such menus prior to
  647.     // when the GUI window is destroyed in case the menu is in use by another window), must be
  648.     // destroyed with DestroyMenu() to ensure a clean exit (resources freed).
  649.     UserMenuItem *menu_item_to_delete;
  650.     for (UserMenuItem *mi = mFirstMenuItem; mi;)
  651.     {
  652.         menu_item_to_delete = mi;
  653.         mi = mi->mNextMenuItem;
  654.         if (menu_item_to_delete->mName != Var::sEmptyString)
  655.             delete menu_item_to_delete->mName; // Since it was separately allocated.
  656.         delete menu_item_to_delete;
  657.     }
  658.     mFirstMenuItem = mLastMenuItem = NULL;
  659.     mMenuItemCount = 0;
  660.     mDefault = NULL;  // i.e. there can't be a *user-defined* default item anymore, even if this is the tray.
  661.     return OK;
  662. }
  663.  
  664.  
  665.  
  666. ResultType UserMenu::ModifyItem(UserMenuItem *aMenuItem, Label *aLabel, UserMenu *aSubmenu, char *aOptions)
  667. // Modify the label, submenu, or options of a menu item (exactly one of these should be NULL and the
  668. // other not except when updating only the options).
  669. // If a menu item becomes a submenu, we don't relinquish its ID in case it's ever made a normal item
  670. // again (avoids the need to re-lookup a unique ID).
  671. {
  672.     if (*aOptions)
  673.         UpdateOptions(aMenuItem, aOptions);
  674.     if (!aLabel && !aSubmenu) // We were called only to update this item's options.
  675.         return OK;
  676.  
  677.     aMenuItem->mLabel = aLabel;  // This will be NULL if this menu item is a separator or submenu.
  678.     if (aMenuItem->mSubmenu == aSubmenu) // Below relies on this check.
  679.         return OK;
  680.     if (!mMenu)
  681.     {
  682.         aMenuItem->mSubmenu = aSubmenu;  // Just set the indicator for when the menu is later created.
  683.         return OK;
  684.     }
  685.  
  686.     // Otherwise, since the OS menu exists, one of these is to be done to aMenuItem in it:
  687.     // 1) Change a submenu to point to a different menu.
  688.     // 2) Change a submenu so that it becomes a normal menu item.
  689.     // 3) Change a normal menu item into a submenu.
  690.  
  691.     // Since Create() ensures that aSubmenu is non-null whenever this->mMenu is non-null, this is just
  692.     // an extra safety check in case some other method destroyed aSubmenu since then:
  693.     if (aSubmenu)
  694.         if (!aSubmenu->Create()) // Create if needed.  No error msg since so rare.
  695.             return FAIL;
  696.  
  697.     MENUITEMINFO mii;
  698.     mii.cbSize = sizeof(mii);
  699.     mii.fMask = MIIM_SUBMENU | MIIM_ID;
  700.     mii.hSubMenu = aSubmenu ? aSubmenu->mMenu : NULL;
  701.     // If this submenu is being back into a normal menu item, the ID must be re-specified
  702.     // because an item that was formerly submenu will not have a real ID due to OS behavior:
  703.     mii.wID = aMenuItem->mMenuID;
  704.     if (SetMenuItemInfo(mMenu, aMenuItem_ID, aMenuItem->mSubmenu != NULL, &mii))
  705.     {
  706.         // Submenu was just made into a different submenu or converted into a normal menu item.
  707.         // Since the OS (as an undocumented side effect) sometimes destroys the menu itself when
  708.         // a submenu is changed in this way, update our state to indicate that the menu handle
  709.         // is no longer valid:
  710.         if (aMenuItem->mSubmenu && aMenuItem->mSubmenu->mMenu && !IsMenu(aMenuItem->mSubmenu->mMenu))
  711.         {
  712.             UserMenu *temp = aMenuItem->mSubmenu;
  713.             aMenuItem->mSubmenu = aSubmenu; // Should be done before the below so that Destroy() sees the change.
  714.             // The following shouldn't fail because submenus are popup menus, and popup menus can't be
  715.             // menu bars. Update: Even if it does fail due to causing a cascade-destroy upward toward any
  716.             // menu bar that happens to own it, it seems okay because the real purpose here is simply to
  717.             // update that fact that "temp" was already destroyed indirectly by the OS, as evidenced by
  718.             // the fact that IsMenu() returned FALSE above.
  719.             temp->Destroy();
  720.         }
  721.         else
  722.             aMenuItem->mSubmenu = aSubmenu;
  723.     }
  724.     // else no error msg and return OK so that the thread will continue.  This may help catch
  725.     // bugs in the course of normal use of this feature.
  726.     return OK;
  727. }
  728.  
  729.  
  730.  
  731. void UserMenu::UpdateOptions(UserMenuItem *aMenuItem, char *aOptions)
  732. {
  733.     if (toupper(*aOptions) == 'P')
  734.         aMenuItem->mPriority = atoi(aOptions + 1);
  735. }
  736.  
  737.  
  738.  
  739. ResultType UserMenu::RenameItem(UserMenuItem *aMenuItem, char *aNewName)
  740. // Caller should specify "" for aNewName to convert aMenuItem into a separator.
  741. // Returns FAIL if the new name conflicts with an existing name.
  742. {
  743.     if (strlen(aNewName) > MAX_MENU_NAME_LENGTH)
  744.         return FAIL; // Caller should diplay error if desired.
  745.  
  746.     if (!mMenu) // Just update the member variables for later use when the menu is created.
  747.         return UpdateName(aMenuItem, aNewName);
  748.  
  749.     MENUITEMINFO mii;
  750.     mii.cbSize = sizeof(mii);
  751.     mii.fMask = MIIM_TYPE;
  752.     mii.dwTypeData = aNewName;
  753.  
  754.     if (*aNewName)
  755.     {
  756.         // Names must be unique only within each menu:
  757.         for (UserMenuItem *mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
  758.             if (!lstrcmpi(mi->mName, aNewName)) // Match found (case insensitive).
  759.                 return FAIL; // Caller should display an error message.
  760.         mii.fType = MFT_STRING;
  761.     }
  762.     else // converting into a separator
  763.     {
  764.         // Notes about the below macro:
  765.         // ID_TRAY_OPEN is not set to be the default for the self-contained version, since it lacks that menu item.
  766.         CHANGE_DEFAULT_IF_NEEDED
  767.         mii.fType = MFT_SEPARATOR;
  768.         if (aMenuItem->mSubmenu)  // Converting submenu into a separator.
  769.         {
  770.             mii.fMask |= MIIM_SUBMENU;
  771.             mii.hSubMenu = NULL;
  772.         }
  773.     }
  774.  
  775.     // Failure is rare enough in the below that no attempt is made to undo the above:
  776.     BOOL result = SetMenuItemInfo(mMenu, aMenuItem_ID, aMenuItem->mSubmenu != NULL, &mii);
  777.     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  778.     return result ? UpdateName(aMenuItem, aNewName) : FAIL;
  779. }
  780.  
  781.  
  782.  
  783. ResultType UserMenu::UpdateName(UserMenuItem *aMenuItem, char *aNewName)
  784. // Caller should already have ensured that aMenuItem is not too long.
  785. {
  786.     size_t new_length = strlen(aNewName);
  787.     if (new_length)
  788.     {
  789.         if (new_length >= aMenuItem->mNameCapacity) // Too small, so reallocate.
  790.         {
  791.             // Use a temp var. so that mName will never wind up being NULL (relied on by other things).
  792.             // This also retains the original menu name if the allocation fails:
  793.             char *temp = new char[new_length + 1];  // +1 for terminator.
  794.             if (!temp)
  795.                 return FAIL;
  796.             // Otherwise:
  797.             if (aMenuItem->mName != Var::sEmptyString) // Since it was previously new'd, delete it.
  798.                 delete aMenuItem->mName;
  799.             aMenuItem->mName = temp;
  800.             aMenuItem->mNameCapacity = new_length + 1;
  801.         }
  802.         strcpy(aMenuItem->mName, aNewName);
  803.     }
  804.     else // It will become a separator.
  805.     {
  806.         *aMenuItem->mName = '\0'; // Safe because even if it's capacity is 1 byte, it's a writable byte.
  807.         aMenuItem->mMenuID = 0; // Free up an ID since separators currently can't be converted back into items.
  808.     }
  809.     return OK;
  810. }
  811.  
  812.  
  813.  
  814. ResultType UserMenu::CheckItem(UserMenuItem *aMenuItem)
  815. // Note that items in a menu bar apparently cannot be checked, so it is not necessary to call
  816. // UPDATE_GUI_MENU_BARS here or in the next few functions below.
  817.  
  818. {
  819.     aMenuItem->mChecked = true;
  820.     if (mMenu)
  821.         CheckMenuItem(mMenu, aMenuItem_ID, aMenuItem_MF_BY | MF_CHECKED);
  822.     return OK;
  823. }
  824.  
  825.  
  826.  
  827. ResultType UserMenu::UncheckItem(UserMenuItem *aMenuItem)
  828. {
  829.     aMenuItem->mChecked = false;
  830.     if (mMenu)
  831.         CheckMenuItem(mMenu, aMenuItem_ID, aMenuItem_MF_BY | MF_UNCHECKED);
  832.     return OK;
  833. }
  834.  
  835.  
  836.  
  837. ResultType UserMenu::ToggleCheckItem(UserMenuItem *aMenuItem)
  838. {
  839.     aMenuItem->mChecked = !aMenuItem->mChecked;
  840.     if (mMenu)
  841.         CheckMenuItem(mMenu, aMenuItem_ID, aMenuItem_MF_BY | (aMenuItem->mChecked ? MF_CHECKED : MF_UNCHECKED));
  842.     return OK;
  843. }
  844.  
  845.  
  846.  
  847. ResultType UserMenu::EnableItem(UserMenuItem *aMenuItem)
  848. {
  849.     aMenuItem->mEnabled = true;
  850.     if (mMenu)
  851.     {
  852.         EnableMenuItem(mMenu, aMenuItem_ID, aMenuItem_MF_BY | MF_ENABLED); // Automatically ungrays it too.
  853.         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  854.     }
  855.     return OK;
  856.  
  857. }
  858.  
  859.  
  860.  
  861. ResultType UserMenu::DisableItem(UserMenuItem *aMenuItem)
  862. {
  863.     aMenuItem->mEnabled = false;
  864.     if (mMenu)
  865.     {
  866.         EnableMenuItem(mMenu,aMenuItem_ID, aMenuItem_MF_BY | MF_DISABLED | MF_GRAYED);
  867.         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  868.     }
  869.     return OK;
  870. }
  871.  
  872.  
  873.  
  874. ResultType UserMenu::ToggleEnableItem(UserMenuItem *aMenuItem)
  875. {
  876.     aMenuItem->mEnabled = !aMenuItem->mEnabled;
  877.     if (mMenu)
  878.     {
  879.         EnableMenuItem(mMenu, aMenuItem_ID, aMenuItem_MF_BY | (aMenuItem->mEnabled ? MF_ENABLED
  880.             : (MF_DISABLED | MF_GRAYED)));
  881.         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  882.     }
  883.     return OK;
  884. }
  885.  
  886.  
  887.  
  888. ResultType UserMenu::SetDefault(UserMenuItem *aMenuItem)
  889. {
  890.     if (mDefault == aMenuItem)
  891.         return OK;
  892.     mDefault = aMenuItem;
  893.     if (!mMenu) // No further action required: the new setting will be in effect when the menu is created.
  894.         return OK;
  895.     if (aMenuItem) // A user-defined menu item is being made the default.
  896.         SetMenuDefaultItem(mMenu, aMenuItem_ID, aMenuItem->mSubmenu != NULL); // This also ensures that only one is default at a time.
  897.     else
  898.     {
  899.         // Otherwise, a user-defined item that was previously the default is no longer the default.
  900.         // Provide a new default if this is the tray menu, the standard items are present, and a default
  901.         // action is called for:
  902.         if (this == g_script.mTrayMenu) // Necessary for proper operation of the self-contained version:
  903. #ifdef AUTOHOTKEYSC
  904.             SetMenuDefaultItem(mMenu, g_AllowMainWindow && mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);
  905. #else
  906.             SetMenuDefaultItem(mMenu, mIncludeStandardItems ? ID_TRAY_OPEN : -1, FALSE);
  907. #endif
  908.         else
  909.             SetMenuDefaultItem(mMenu, -1, FALSE);
  910.     }
  911.     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Testing shows that menu bars themselves can have default items, and that this is necessary.
  912.     return OK;
  913. }
  914.  
  915.  
  916.  
  917. ResultType UserMenu::IncludeStandardItems()
  918. {
  919.     if (mIncludeStandardItems)
  920.         return OK;
  921.     // In this case, immediately create the menu to support having the standard menu items on the
  922.     // bottom or middle rather than at the top (which is the default). Older comment: Only do
  923.     // this if it was false beforehand so that the standard menu items will be appended to whatever
  924.     // the user has already added to the tray menu (increases flexibility).
  925.     if (!Create()) // It may already exist, in which case this returns OK.
  926.         return FAIL; // No error msg since so rare.
  927.     return AppendStandardItems();
  928. }
  929.  
  930.  
  931.  
  932. ResultType UserMenu::ExcludeStandardItems()
  933. {
  934.     if (!mIncludeStandardItems)
  935.         return OK;
  936.     mIncludeStandardItems = false;
  937.     return Destroy(); // It will be recreated automatically the next time the user displays it.
  938. }
  939.  
  940.  
  941.  
  942. ResultType UserMenu::Create(MenuTypeType aMenuType)
  943. // Menu bars require non-popup menus (CreateMenu vs. CreatePopupMenu).  Rather than maintain two
  944. // different types of HMENUs on the rare chance that a script might try to use a menu both as
  945. // a popup and a menu bar, it seems best to have only one type to keep the code simple and reduce
  946. // resources used for the menu.  This has been documented in the help file.
  947. // Note that a menu bar's submenus can be (perhaps must be) of the popup type, so we only need
  948. // to worry about the distinction for the menu bar itself.  The caller tells us which is which.
  949. {
  950.     if (mMenu)
  951.     {
  952.         // Since menu already exists, check if it's the right type.  If caller left the type unspecified,
  953.         // assume it is the right type:
  954.         if (aMenuType == MENU_TYPE_NONE || aMenuType == mMenuType)
  955.             return OK;
  956.         else // It exists but it's the wrong type.  Destroy and recreate it (but keep TRAY always as popup type).
  957.             if (!stricmp(mName, "tray") || !Destroy()) // Could not be destroyed, perhaps because it is attached to a window as a menu bar.
  958.                 return FAIL;
  959.     }
  960.     if (aMenuType == MENU_TYPE_NONE) // Since caller didn't specify and it's about to be (re)created, assume popup.
  961.         aMenuType = MENU_TYPE_POPUP;
  962.     if (   !(mMenu = (aMenuType == MENU_TYPE_BAR) ? CreateMenu() : CreatePopupMenu())   )
  963.         // Failure is rare, so no error msg here (caller can, if it wants).
  964.         return FAIL;
  965.  
  966.     mMenuType = aMenuType;  // We have to track its type since I don't think there's any way to find out via API.
  967.  
  968.     // It seems best not to have a mandatory EXIT item added to the bottom of the tray menu
  969.     // for these reasons:
  970.     // 1) Allows the tray icon to be shown even at time when the user wants it to have no menu at all
  971.     //    (i.e. avoids the need for #NoTrayIcon just to disable the showing of the menu).
  972.     // 2) Avoids complexity because there would be a 3rd state: Standard, NoStandard, and
  973.     //    NoStandardWithExit.  This might be inconsequential, but would require testing.
  974.     //if (!mIncludeStandardItems && !mMenuItemCount)
  975.     //{
  976.     //    AppendMenu(mTrayMenu->mMenu, MF_STRING, ID_TRAY_EXIT, "E&xit");
  977.     //    return OK;
  978.     //}
  979.  
  980.     // By default, the standard menu items are added first, since the users would probably want
  981.     // their own user defined menus at the bottom where they're easier to reach:
  982.     if (mIncludeStandardItems)
  983.         AppendStandardItems();
  984.  
  985.     // Now append all of the user defined items:
  986.     UINT flags;
  987.     UserMenuItem *mi;
  988.     for (mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
  989.     {
  990.         flags = 0;
  991.         if (!*mi->mName)
  992.             flags |= MF_SEPARATOR;  // MF_STRING is the default.
  993.         if (!mi->mEnabled)
  994.             flags |= (MF_DISABLED | MF_GRAYED);  // MF_ENABLED is the default.
  995.         if (mi->mChecked)   // MF_UNCHECKED is the default.
  996.             flags |= MF_CHECKED;
  997.         if (mi->mSubmenu)
  998.         {
  999.             flags |= MF_POPUP;
  1000.             // Ensure submenu is created so that handle can be used below.
  1001.             if (!mi->mSubmenu->Create())
  1002.                 return FAIL;
  1003.         }
  1004.         AppendMenu(mMenu, flags, mi->mSubmenu ? (UINT_PTR)mi->mSubmenu->mMenu : mi->mMenuID, mi->mName);
  1005.     }
  1006.     if (mDefault)
  1007.         // This also automatically ensures that only one is default at a time:
  1008.         SetMenuDefaultItem(mMenu, mDefault->mMenuID, FALSE);
  1009.  
  1010.     // Apply background color if this menu has a non-standard one.  If this menu has submenus,
  1011.     // they will be individually given their own background color when created via Create(),
  1012.     // which is why false is passed:
  1013.     ApplyColor(false);
  1014.  
  1015.     return OK;
  1016. }
  1017.  
  1018.  
  1019.  
  1020. void UserMenu::SetColor(char *aColorName, bool aApplyToSubmenus)
  1021. {
  1022.     // Avoid the overhead of creating HBRUSH's on OSes that don't support SetMenuInfo().
  1023.     // Perhaps there is some other way to change menu background color on Win95/NT?
  1024.     if (g_os.IsWin95() || g_os.IsWinNT4())
  1025.         return;
  1026.     AssignColor(aColorName, mColor, mBrush);  // Takes care of deleting old brush, etc.
  1027.     // To avoid complications, such as a submenu being detached from its parent and then its parent
  1028.     // later being being deleted (which causes the HBRUSH to get deleted too), give each submenu it's
  1029.     // own HBRUSH handle by calling AssignColor() for each:
  1030.     if (aApplyToSubmenus)
  1031.         for (UserMenuItem *mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
  1032.             if (mi->mSubmenu)
  1033.                 AssignColor(aColorName, mi->mSubmenu->mColor, mi->mSubmenu->mBrush);
  1034.     if (mMenu)
  1035.     {
  1036.         ApplyColor(aApplyToSubmenus);
  1037.         UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary.
  1038.     }
  1039. }
  1040.  
  1041.  
  1042.  
  1043. void UserMenu::ApplyColor(bool aApplyToSubmenus)
  1044. // Caller has ensured that mMenu is not NULL.
  1045. // The below should be done even if the default color is being (re)applied because
  1046. // testing shows that the OS sets the color to white if the HBRUSH becomes invalid.
  1047. // The caller is also responsible for calling UPDATE_GUI_MENU_BARS if desired.
  1048. {
  1049.     // Must fetch function address dynamically or program won't launch at all on Win95/NT:
  1050.     typedef BOOL (WINAPI *MySetMenuInfoType)(HMENU, LPCMENUINFO);
  1051.     static MySetMenuInfoType MySetMenuInfo = (MySetMenuInfoType)GetProcAddress(GetModuleHandle("user32"), "SetMenuInfo");
  1052.     if (!MySetMenuInfo)
  1053.         return;
  1054.     MENUINFO mi = {0}; 
  1055.     mi.cbSize = sizeof(MENUINFO);
  1056.     mi.fMask = MIM_BACKGROUND|(aApplyToSubmenus ? MIM_APPLYTOSUBMENUS : 0);
  1057.     mi.hbrBack = mBrush;
  1058.     MySetMenuInfo(mMenu, &mi);
  1059. }
  1060.  
  1061.  
  1062.  
  1063. ResultType UserMenu::AppendStandardItems()
  1064. // Caller must ensure that this->mMenu exists if it wants the items to be added immediately.
  1065. {
  1066.     mIncludeStandardItems = true; // even if the menu doesn't exist.
  1067.     if (!mMenu)
  1068.         return OK;
  1069. #ifdef AUTOHOTKEYSC
  1070.     if (g_AllowMainWindow)
  1071.     {
  1072.         AppendMenu(mMenu, MF_STRING, ID_TRAY_OPEN, "&Open");
  1073.         if (this == g_script.mTrayMenu && !mDefault) // No user-defined default menu item, so use the standard one.
  1074.             SetMenuDefaultItem(mMenu, ID_TRAY_OPEN, FALSE); // Seems to have no function other than appearance.
  1075.     }
  1076. #else
  1077.     AppendMenu(mMenu, MF_STRING, ID_TRAY_OPEN, "&Open");
  1078.     AppendMenu(mMenu, MF_STRING, ID_TRAY_HELP, "&Help");
  1079.     AppendMenu(mMenu, MF_SEPARATOR, 0, NULL);
  1080.     AppendMenu(mMenu, MF_STRING, ID_TRAY_WINDOWSPY, "&Window Spy");
  1081.     AppendMenu(mMenu, MF_STRING, ID_TRAY_RELOADSCRIPT, "&Reload This Script");
  1082.     AppendMenu(mMenu, MF_STRING, ID_TRAY_EDITSCRIPT, "&Edit This Script");
  1083.     AppendMenu(mMenu, MF_SEPARATOR, 0, NULL);
  1084.     if (this == g_script.mTrayMenu && !mDefault) // No user-defined default menu item, so use the standard one.
  1085.         SetMenuDefaultItem(mMenu, ID_TRAY_OPEN, FALSE); // Seems to have no function other than appearance.
  1086. #endif
  1087.     AppendMenu(mMenu, MF_STRING, ID_TRAY_SUSPEND, "&Suspend Hotkeys");
  1088.     AppendMenu(mMenu, MF_STRING, ID_TRAY_PAUSE, "&Pause Script");
  1089.     AppendMenu(mMenu, MF_STRING, ID_TRAY_EXIT, "E&xit");
  1090.     UPDATE_GUI_MENU_BARS(mMenuType, mMenu)  // Verified as being necessary (though it would be rare anyone would want the menu bar containing the std items).
  1091.     return OK;  // For caller convenience.
  1092. }
  1093.  
  1094.  
  1095.  
  1096. ResultType UserMenu::Destroy()
  1097. // Returns OK upon complete success or FAIL otherwise.  For example, even if this's menu
  1098. // is successfully destroyed, if the indirect destructions resulting from it don't succeed, this
  1099. // method returns FAIL.
  1100. {
  1101.     if (!mMenu)  // For performance.
  1102.         return OK;
  1103.     // I think DestroyMenu() can fail if an attempt is made to destroy the menu while it is being
  1104.     // displayed (but even if it doesn't fail, it seems very bad to try to destroy it then, which
  1105.     // is why g_MenuIsVisible is checked just to be sure).
  1106.     // But this all should be impossible in our case because the script is in an uninterruptible state
  1107.     // while the menu is displayed, which in addition to pausing the current thread (which happens
  1108.     // anyway), no new timed or hotkey subroutines can be launched.  Thus, this should rarely if
  1109.     // ever happen, which is why no error message is given here:
  1110.     //if (g_MenuIsVisible)
  1111.     //    return FAIL;
  1112.  
  1113.     // DestroyMenu fails (GetLastError() == ERROR_INVALID_MENU_HANDLE) if a parent menu that contained
  1114.     // mMenu as one of its submenus was destroyed above.  This seems to indicate that submenus are
  1115.     // destroyed whenever a parent menu is destroyed.  Therefore, don't check failure on the below,
  1116.     // just assume that afterward, the menu is gone.  IsMenu() is checked because the handle can be
  1117.     // invalid if the OS already destroyed it behind-the-scenes (this happens to a submenu whenever
  1118.     // its parent menu is destroyed, or whenever a submenu is converted back into a normal menu item):
  1119.     if (IsMenu(mMenu))
  1120.     {
  1121.         // As a precaution, don't allow a menu to be destroyed if a window is using it as its
  1122.         // menu bar. That might have bad side-effects on some OSes, especially older ones:
  1123.         if (mMenuType == MENU_TYPE_BAR && GuiType::sGuiCount)
  1124.         {
  1125.             int i, gui_count;
  1126.             for (i = 0, gui_count = 0; i < MAX_GUI_WINDOWS; ++i)
  1127.                 if (g_gui[i])
  1128.                 {
  1129.                     if (g_gui[i]->mHwnd && GetMenu(g_gui[i]->mHwnd) == mMenu)
  1130.                         return FAIL; // A GUI window is using this menu, so don't destroy the menu.
  1131.                     if (GuiType::sGuiCount == ++gui_count) // No need to keep searching.
  1132.                         break;
  1133.                 }
  1134.         }
  1135.         if (!DestroyMenu(mMenu)) // v1.0.30.01: Doesn't seem to be a reason *not* to check the return value and return FAIL if it failed.
  1136.             return FAIL;
  1137.     }
  1138.     mMenu = NULL; // This must be done immediately after destroying the menu to prevent recursion problems below.
  1139.  
  1140.     // Bug-fix for v1.0.19: The below is now done OUTSIDE the above block because the moment a
  1141.     // parent menu is deleted all its submenus AND SUB-SUB-SUB...MENUS become invalid menu handles.
  1142.     // But even though the OS has done this, Destroy() must still be called recursively from here
  1143.     // so that the menu handles will be set to NULL.  This is because other functions -- such as
  1144.     // Display() -- do not do the IsMenu() check, relying instead on whether the handle is NULL to
  1145.     // determine whether the menu physically exists.
  1146.     // The moment the above is done, any submenus that were attached to mMenu are also destroyed
  1147.     // by the OS.  So mark them as destroyed in our bookkeeping also:
  1148.     UserMenuItem *mi;
  1149.     for (mi = mFirstMenuItem; mi ; mi = mi->mNextMenuItem)
  1150.         if (mi->mSubmenu && mi->mSubmenu->mMenu && !IsMenu(mi->mSubmenu->mMenu))
  1151.             mi->mSubmenu->Destroy(); // Its return value isn't checked since there doesn't seem to be anything that can/should be done if it fails.
  1152.  
  1153.     // Destroy any menu that contains this menu as a submenu.  This is done so that such
  1154.     // menus will be automatically recreated the next time they are used, which is necessary
  1155.     // because otherwise when such a menu is displayed the next time, the OS will show its
  1156.     // old contents even though the menu is gone.  Thus, those old menu items will be
  1157.     // selectable but will have no effect.  In addition, sometimes our caller plans to
  1158.     // recreate this->mMenu (or have it recreated automatically upon first use) and thus
  1159.     // we don't want to use DeleteMenu() because that would require having to detect whether
  1160.     // the menu needs updating (to reflect whether the submenu has been recreated) every
  1161.     // time we display it.  Another drawback to DeleteMenu() is that it would change the
  1162.     // order of the menu items to something other than what the user originally specified
  1163.     // unless InsertMenu() was woven in during the update:
  1164.     ResultType result = OK;
  1165.     for (UserMenu *m = g_script.mFirstMenu; m; m = m->mNextMenu)
  1166.         if (m->mMenu)
  1167.             for (mi = m->mFirstMenuItem; mi; mi = mi->mNextMenuItem)
  1168.                 if (mi->mSubmenu == this)
  1169.                     if (!m->Destroy())  // Attempt to destroy any menu that contains this menu as a submenu (will fail if m is a menu bar).
  1170.                         result = FAIL; // Seems best to consider even one failure is considered a total failure.
  1171.     return result;
  1172. }
  1173.  
  1174.  
  1175.  
  1176. ResultType UserMenu::Display(bool aForceToForeground, int aX, int aY)
  1177. // aForceToForeground defaults to true because when a menu is displayed spontanesouly rather than
  1178. // in response to the user right-clicking the tray icon, I believe that the OS will revert to its
  1179. // behavior of "resisting" a window that tries to "steal focus".  I believe this resistance does
  1180. // not occur when the user clicks the icon because that click causes the task bar to get focus,
  1181. // and it is likely that the OS allows other windows to steal focus from the task bar without
  1182. // resistence.  This is done because if the main window is *not* successfully activated prior to
  1183. // displaying the menu, it might be impossible to dismiss the menu by clicking outside of it.
  1184. {
  1185.     if (!mMenuItemCount && !mIncludeStandardItems)
  1186.         return OK;  // Consider the display of an empty menu to be a success.
  1187.     //if (!IsMenu(mMenu))
  1188.     //    mMenu = NULL;
  1189.     if (!mMenu) // i.e. because this is the first time the user has opened the menu.
  1190.         if (!Create()) // no error msg since so rare
  1191.             return FAIL;
  1192.     if (this == g_script.mTrayMenu)
  1193.     {
  1194.         // These are okay even if the menu items don't exist (perhaps because the user customized the menu):
  1195.         CheckMenuItem(mMenu, ID_TRAY_SUSPEND, g_IsSuspended ? MF_CHECKED : MF_UNCHECKED);
  1196.         CheckMenuItem(mMenu, ID_TRAY_PAUSE, g.IsPaused ? MF_CHECKED : MF_UNCHECKED);
  1197.     }
  1198.  
  1199.     POINT pt;
  1200.     if (aX == COORD_UNSPECIFIED || aY == COORD_UNSPECIFIED)
  1201.         GetCursorPos(&pt);
  1202.     if (!(aX == COORD_UNSPECIFIED && aY == COORD_UNSPECIFIED)) // At least one was specified.
  1203.     {
  1204.         if (aX != COORD_UNSPECIFIED)
  1205.             pt.x = aX;
  1206.         if (aY != COORD_UNSPECIFIED)
  1207.             pt.y = aY;
  1208.         if (!(g.CoordMode & COORD_MODE_MENU))  // Using coords relative to the active window (rather than screen).
  1209.             WindowToScreen((int &)pt.x, (int &)pt.y);
  1210.     }
  1211.  
  1212.     // UPDATE: For v1.0.35.14, must ensure one of the script's windows is active before showing the menu
  1213.     // because otherwise the menu cannot be dismissed via the escape key or by clicking outside the menu.
  1214.     // Testing shows that ensuring any of our thread's windows is active allows both the tray menu and
  1215.     // any popup or context menus to work correctly.
  1216.     // UPDATE: For v1.0.35.12, the script's main window (g_hWnd) is activated only for the tray menu because:
  1217.     // 1) Doing so for GUI context menus seems to prevent mouse clicks in the menu or elsewhere in the window.
  1218.     // 2) It would probably have other side effects for other uses of popup menus.
  1219.     HWND fore_win = GetForegroundWindow();
  1220.     bool change_fore;
  1221.     if (change_fore = (!fore_win || GetWindowThreadProcessId(fore_win, NULL) != g_MainThreadID))
  1222.     {
  1223.         // Always bring main window to foreground right before TrackPopupMenu(), even if window is hidden.
  1224.         // UPDATE: This is a problem because SetForegroundWindowEx() will restore the window if it's hidden,
  1225.         // but restoring also shows the window if it's hidden.  Could re-hide it... but the question here
  1226.         // is can a minimized window be the foreground window?  If not, how to explain why
  1227.         // SetForegroundWindow() always seems to work for the purpose of the tray menu?
  1228.         //if (aForceToForeground)
  1229.         //{
  1230.         //    // Seems best to avoid using the script's current setting of #WinActivateForce.  Instead, always
  1231.         //    // try the gentle approach first since it is unlikely that displaying a menu will cause the
  1232.         //    // "flashing task bar button" problem?
  1233.         //    bool original_setting = g_WinActivateForce;
  1234.         //    g_WinActivateForce = false;
  1235.         //    SetForegroundWindowEx(g_hWnd);
  1236.         //    g_WinActivateForce = original_setting;
  1237.         //}
  1238.         //else
  1239.         if (!SetForegroundWindow(g_hWnd))
  1240.         {
  1241.             // The below fixes the problem where the menu cannot be canceled by clicking outside of
  1242.             // it (due to the main window not being active).  That usually happens the first time the
  1243.             // menu is displayed after the script launches.  0 is not enough sleep time, but 10 is:
  1244.             SLEEP_WITHOUT_INTERRUPTION(10);
  1245.             SetForegroundWindow(g_hWnd);  // 2nd time always seems to work for this particular window.
  1246.             // OLDER NOTES:
  1247.             // Always bring main window to foreground right before TrackPopupMenu(), even if window is hidden.
  1248.             // UPDATE: This is a problem because SetForegroundWindowEx() will restore the window if it's hidden,
  1249.             // but restoring also shows the window if it's hidden.  Could re-hide it... but the question here
  1250.             // is can a minimized window be the foreground window?  If not, how to explain why
  1251.             // SetForegroundWindow() always seems to work for the purpose of displaying the tray menu?
  1252.             //if (aForceToForeground)
  1253.             //{
  1254.             //    // Seems best to avoid using the script's current setting of #WinActivateForce.  Instead, always
  1255.             //    // try the gentle approach first since it is unlikely that displaying a menu will cause the
  1256.             //    // "flashing task bar button" problem?
  1257.             //    bool original_setting = g_WinActivateForce;
  1258.             //    g_WinActivateForce = false;
  1259.             //    SetForegroundWindowEx(g_hWnd);
  1260.             //    g_WinActivateForce = original_setting;
  1261.             //}
  1262.             //else
  1263.             //...
  1264.         }
  1265.     }
  1266.     // Apparently, the HWND parameter of TrackPopupMenuEx() can be g_hWnd even if one of the script's
  1267.     // other (non-main) windows is foreground. The menu still seems to operate correctly.
  1268.     g_MenuIsVisible = MENU_TYPE_POPUP; // It seems this is also set by HANDLE_MENU_LOOP because apparently, TrackPopupMenuEx generates WM_ENTERMENULOOP. So it's done here just for added safety in case WM_ENTERMENULOOP isn't ALWAYS generated.
  1269.     TrackPopupMenuEx(mMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, g_hWnd, NULL);
  1270.     g_MenuIsVisible = MENU_TYPE_NONE;
  1271.     // MSDN recommends this to prevent menu from closing on 2nd click.  MSDN also says that it's only
  1272.     // necessary to do this "for a notification icon". So to to avoid unnecessary launches of MsgSleep(),
  1273.     // its done only for the tray menu in v1.0.35.12:
  1274.     if (this == g_script.mTrayMenu)
  1275.         PostMessage(g_hWnd, WM_NULL, 0, 0);
  1276.     else // Seems best to avoid the following for the tray menu since it doesn't seem work and might produce side-effects in some cases.
  1277.     {
  1278.         if (change_fore && fore_win && GetForegroundWindow() == g_hWnd)
  1279.         {
  1280.             // The last of the conditions above is checked in case the user clicked the taskbar or some
  1281.             // other window to dismiss the menu.  In that case, the following isn't done because it typically
  1282.             // steals focus from the user's intended window, and this attempt usually fails due to the OS's
  1283.             // anti-focus-stealing measure, which in turn would cause fore_win's taskbar button to flash annoyingly.
  1284.             SetForegroundWindow(fore_win); // See comments above for why SetForegroundWindowEx() isn't used.
  1285.             // The following resolves the issue where the window would not have enough time to become active
  1286.             // before we continued using our timeslice to return to our caller and launch our new thread.
  1287.             // In other words, the menu thread would launch before SetForegroundWindow() actually had a chance
  1288.             // to take effect:
  1289.             // 0 is exactly the amount of time (-1 is not enough because it doesn't yeild) needed for that
  1290.             // other process to actually ack/perform the activation of its window and clean out its queue using
  1291.             // one timeslice.  This has been tested even when the CPU is maxed from some third-party process.
  1292.             // For performance and code simplicity, it seems best not to do a GetForegroundWindow() loop that
  1293.             // waits for it to become active (unless others report that this method is significantly unreliable):
  1294.             SLEEP_WITHOUT_INTERRUPTION(0);
  1295.         }
  1296.     }
  1297.     // Fix for v1.0.38.05: If the current thread is interruptible (which it should be since a menu was just
  1298.     // displayed, which almost certainly timed out the default Thread Interrupt setting), the following
  1299.     // MsgSleep() will launch the selected menu item's subroutine.  This fix is needed because of a change
  1300.     // in v1.0.38.04, namely the line "g_script.mLastPeekTime = tick_now;" in IsCycleComplete().
  1301.     // The root problem here is that it would not be intuitive to allow the command after
  1302.     // "Menu, MyMenu, Show" should to run before the menu item's subroutine launches as a new thread.
  1303.     // 
  1304.     // You could argue that selecting a menu item should immediately Gosub the selected menu item's
  1305.     // subroutine rather than queuing it up as a new thread.  However, even if that is a better method,
  1306.     // it would break existing scripts that rely on new-thread behavior (such as fresh default for
  1307.     // SetKeyDelay).
  1308.     //
  1309.     // Without this fix, a script such as the following (and many other things similar) would
  1310.     // counterintuitively fail to launch the selected item's subroutine:
  1311.     // Menu, MyMenu, Add, NOTEPAD
  1312.     // Menu, MyMenu, Show
  1313.     // ; Sleep 0  ; Uncommenting this line was necessary in v1.0.38.04 but not any other versions.
  1314.     // ExitApp
  1315.     MsgSleep(-1);
  1316.     return OK;
  1317. }
  1318.  
  1319.  
  1320.  
  1321. UINT UserMenu::GetSubmenuPos(HMENU ahMenu)
  1322. // ahMenu will be searched for in this->mMenu.
  1323. // Returns UINT_MAX if this->mMenu is NULL, ahMenu is NULL, or if ahMenu can't be found in this->mMenu.
  1324. // Testing shows that neither ModifyMenu() nor SetMenuItemInfo() nor any other Menu modifying
  1325. // API call will accept aMenuItem->mMenuID or aMenuItem->mSubmenu as a way to uniquely
  1326. // indentify the menu item we want to change (even though GetMenuItemInfo indicates that
  1327. // aMenuItem->mSubmenu is the "ID" of sorts).  Thus, the menu item can only be modified
  1328. // by position.  Rather than having to maintain each submenu's position in every menu, thus
  1329. // making the code less maintainable since you always have to worry about standard menu items
  1330. // being added to the top vs. bottom of the menu, other menu items being inserted via
  1331. // InsertMenu() (if that is ever allowed), etc. -- just loop through the menu to find the
  1332. // right item, then return that to the caller so that it can modify the submenu based on position:
  1333. {
  1334.     if (!ahMenu || !mMenu)
  1335.         return UINT_MAX;
  1336.     int menu_item_count = GetMenuItemCount(mMenu);
  1337.     for (int i = 0; i < menu_item_count; ++i)
  1338.         if (GetSubMenu(mMenu, i) == ahMenu)
  1339.             return i;
  1340.     return UINT_MAX;
  1341. }
  1342.  
  1343.  
  1344.  
  1345. UINT UserMenu::GetItemPos(char *aMenuItemName)
  1346. // aMenuItemName will be searched for in this->mMenu.
  1347. // Returns UINT_MAX if this->mMenu is NULL or if aMenuItemName can't be found in this->mMenu.
  1348. {
  1349.     if (!mMenu)
  1350.         return UINT_MAX;
  1351.     int menu_item_count = GetMenuItemCount(mMenu);
  1352.     char buf[MAX_MENU_NAME_LENGTH + 2];  // +2 due to uncertainty over whether GetMenuString()'s nMaxCount includes room for terminator.
  1353.     for (int i = 0; i < menu_item_count; ++i)
  1354.         if (GetMenuString(mMenu, i, buf, sizeof(buf) - 1, MF_BYPOSITION))
  1355.             if (!lstrcmpi(buf, aMenuItemName))  // A case insensitive match was found.
  1356.                 return i;
  1357.     return UINT_MAX;  // No match found.
  1358. }
  1359.  
  1360.  
  1361.  
  1362. bool UserMenu::ContainsMenu(UserMenu *aMenu)
  1363. {
  1364.     if (!aMenu)
  1365.         return false;
  1366.     // For each submenu in mMenu: Check if it or any of its submenus equals aMenu.
  1367.     for (UserMenuItem *mi = mFirstMenuItem; mi; mi = mi->mNextMenuItem)
  1368.         if (mi->mSubmenu)
  1369.             if (mi->mSubmenu == aMenu || mi->mSubmenu->ContainsMenu(aMenu)) // recursive
  1370.                 return true;
  1371.             //else keep searching
  1372.     return false;
  1373. }